package com.example.security.config;

import com.example.security.config.component.DynamicSecurityFilter;
import com.example.security.config.component.JwtAuthenticationTokenFilter;
import com.example.security.config.component.RestAuthenticationEntryPoint;
import com.example.security.config.component.RestfulAccessDeniedHandler;
import com.example.security.service.impl.UserServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;


/**
 * 对SpringSecurity的配置的扩展，支持自定义白名单资源路径和查询用户逻辑
 */
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled=true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;

    @Autowired
    private RestfulAccessDeniedHandler restfulAccessDeniedHandler;

    @Autowired
    private RestAuthenticationEntryPoint restAuthenticationEntryPoint;

    @Autowired
    private IgnoreUrlsConfig ignoreUrlsConfig;

    @Autowired
    private UserServiceImpl userService;

    @Autowired
    private DynamicSecurityFilter dynamicSecurityFilter;


    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userService).passwordEncoder(passwordEncoder());
    }

    @Override
    protected void configure(HttpSecurity httpSecurity) throws Exception {
        ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry registry = httpSecurity
                .authorizeRequests();
        /**
         * anonymous(): 允许匿名用户访问，不允许已登入用户访问
         * permitAll(): 不管登入与不登入，都能访问
         */
        //允许这些资源路径可直接访问
        for (String url : ignoreUrlsConfig.getUrls()) {
            registry.antMatchers(url).permitAll();
        }
        //允许跨域请求的OPTIONS请求
        registry.antMatchers(HttpMethod.OPTIONS).permitAll();

        registry.and()
                // 其他任何请求都需要身份认证
                .authorizeRequests()
                .anyRequest()
                .authenticated()

                // 关闭跨站请求防护
                .and()
                .csrf()
                .disable()
                // 允许跨域
                .cors().disable()

                // 不使用session，无状态
                .sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)

                // 【自定义拦截器】
                .and()
                // 【拦截】在UsernamePasswordAuthenticationFilter(用户认证拦截器)之前添加JWT登录授权拦截器
                .addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class)
                // 【拦截】在FilterSecurityInterceptor(权限认证拦截器)之前添加动态权限处理拦截器
                .addFilterBefore(dynamicSecurityFilter, FilterSecurityInterceptor.class)

                // 【开启自定义异常处理】
                .exceptionHandling()
                // 【异常】无权限访问
                .accessDeniedHandler(restfulAccessDeniedHandler)
                // 【异常】未登录或登录过期
                .authenticationEntryPoint(restAuthenticationEntryPoint);
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Bean
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

}
